理解 DOM

1. 什么是 DOM(Document Object Model / 文档对象模型)?

这是 W3C 对 DOM 的描述:

The Document Object Model is a platform- and language-neutral interface that will allow programs and scripts to dynamically access and update the content, structure and style of documents. The document can be further processed and the results of that processing can be incorporated back into the presented page.

文档对象模型是一个与平台和语言无关的通用的应用程序接口(API),我们可以通过这个接口利用程序和脚本去动态地修改文档的内容、结构和样式。

就像一张城市街道地图可以代表一个实际存在的城市那样,文档对象模型可以视为一张网页的地图,代表着浏览器窗口的当前网页。

2. 节点

在 DOM 中,文档是由节点构成的集合,只不过这时的节点分散在文档树上,以文档树的树枝和树叶的形式存在。一份文档就是一棵节点树。

在 DOM 里有许多不同类型的节点。主要有文档节点、元素节点、特性节点和文本节点。

每个节点都是一个对象,这些对象中的每一个还天生具有一系列非常有用的属性和方法,供我们检索甚至改变对象中包含的信息。

3. 所有节点共享的属性和方法

JavaScript 中的所有节点类型都继承自 Node 类型,因此所有节点类型都共享着相同的基本属性和方法。

3.1 共享属性

  • nodeType
    该属性用于表明节点的类型
节点名称 对应字符 对应数字
元素节点 Node.ELEMENT_NODE 1
特性节点 Node.ATTRIBUTE_NODE 2
文本节点 Node.TEXT_NODE 3

在判断节点类型时建议用数字形式,因为 IE 不兼容字符形式

  • node.nodeName
    根据节点的类型,返回不同的值
节点名称 对应值
元素节点 元素大写标签名,如 “DIV”
特性节点 特性的名称
文本节点 “#text”
文档节点 “#document”
  • node.nodeValue
    只有文本节点的该属性值为对应的文本内容,其他节点都为 null
  • node.parentNode
    值为该节点的父节点
  • node.childNodes
    值为一个 NodeList 类数组 ,保存了该节点的所有子节点
  • node.firstChild
    值为该节点的第一个子节点
  • node.lastChild
    值为该节点的最后一个子节点
  • node.previousSibling
    值为该节点的上一个节点(同一层级)
  • node.nextSibling
    值为该节点的下一个节点(同一层级)

3.2 共享方法

  • node.appendChild(newNode)
    在该节点的最后一个子节点后插入新节点
  • node.removeChild(someNode)
    删除该节点的某个子节点
  • node.insertBefore(newNode, someNode)
    在该节点的指定子节点前插入新节点
  • node.replaceChild(newNode, someNode)
    用一新节点替换该节点的指定子节点
  • node.cloneNode(true/false)
    可以得到节点的一个完全相同的副本,圆括号里是 true 时,执行深复制,也就是复制节点及其整个子节点树;圆括号里是 false 时,执行浅复制,只复制节点本身。
    这个方法只会复制 DOM 节点中原有的属性,不会复制通过 JavaScript 添加的属性,例如事件处理程序。但是 IE 在此有个 bug,它会复制事件处理程序,所以在执行复制前最好移除事件处理程序。
  • node.normalize()
    这个方法用来处理文档树中的文本节点。如果找到空文本节点,则删除它;如果找到相邻的文本节点,则将它们合并为一个文本节点。

4. document 节点

document 对象是 HTMLDocument 的一个实例, 是window 对象的一个属性, 表示整个 HTML 页面。

nodeType 为 9; nodeName 为 “#document”; nodeValue 为 null; parentNode 为null;

通过这个文档对象,不仅可以取得与页面有关的信息,而且还能操作页面的外观及其底层结构。

4.1 文档对象的子节点

子节点可以是:DocumentType(<!DOCTYPE>)、Element(< html >)、Comment。

获取 元素:

var html = document.documentElement;
alert(html === document.childNode[0]); // true
alert(html === document.firstChild); // true

上面代码我们可以看出,可以通过三种方法获取 元素节点。
所有浏览器都支持第一种方法,通过 document 对象的 documentElement 属性获取;但是不是所有浏览器都支持后面两种方法,后面两种方法有很能获取到 <!DOCTYPE> 或注释节点。
所以,最可靠的获取 元素的方式还是第一种方式。

另外,document 对象还有一个 body 属性,直接指向 元素,虽然 元素并不是它的子节点。所有浏览器也都支持这种方法

var body = document.body; // 取得对 <body> 的引用

4.2 文档信息

// 获取文档标题并重新设置标题
var originalTitle = document.title;
var originalTitle = "New page title";
// 获取完整的 URL(地址栏中显示的 URL)
var url = document.URL;
// 获取来源页面的 URL
var sourceUrl = document.referrer;
// 获取域名
var url = document.domain;

4.3 查找元素

  1. document.getElementById()
    返回具有这个 id 的元素对象
  2. document.getElementsByTagName()
    返回一个 HTMLCollection 类数组,里面包含了具有这个标签名的所有元素节点
  3. document.getElementsByClassName()
    返回一个 HTMLCollection 类数组,里面包含了具有这个类名的所有元素节点
    但是这个方法是 HTML5 新加的,一些版本较低的浏览器可能不支持,下面的方法适合新老浏览器:

    function getElementsByClassName(elem, classname) {
    if (document.getElementsByClassName) {
    return document.getElementsByClassName(classname);
    } else {
    var results = new Array();
    var elems = document.getElementsByTagName(classname);
    for (var i=0; i < elems.length; i++) {
    if (elems[i].className.indexOf(classname) !== -1) {
    results[elems.length] = elems[i];
    };
    };
    };
    return results;
    }

    这种方法的基本思路是利用 document.getElementsByTagName() 方法获取到所有具有相同标签的元素。
    然后挨个检查这些元素的 className 属性(对应的是 HTML 标签中的 class 特性)中是否有要获取的 classname。
    如果有的话,就添加到新创建的一个数组中,对应的索引值为当前数组的长度。
    最后返回这个数组。

  4. document.getElementsByName()
    这个方法会返回带有给定 name 特性的所有元素。这个方法最常用的情况是取得单选按钮;为了确保发送给浏览器的值确定无误,所有单选按钮必须具有相同的 name 特性,如下面例子所示:

    <fieldset>
    <legend>Which color do you prefer?</legend>
    <ul>
    <li><input type="radio" value="red" name="color" id="colorRed">
    <label for="colorRed">Red</label></li>
    <li><input type="radio" value="green" name="color" id="colorGreen">
    <label for="colorGreen">Green</label></li>
    <li><input type="radio" value="blue" name="color" id="colorBlue">
    <label for="colorBlue">Blue</label></li>
    </ul>
    </fieldset>

    其中所有的单选按钮的 name 的特性值都是”color”,但它们的 ID 可以不同。ID 的作用在于将

    var radios = document.getElementsByName("color");

    返回的是一个 HTMLCollection 类数组。

4.4 创建其它节点

  1. 创建元素节点
    使用 document.createElement() 方法可以创建新元素。这个方法接受一个参数,即要创建的元素的标签名。

    var div = document.createElement("div");
    div.id = "myNewDiv";
    div.className = "box";
    document.body.appendChild(div);

    上面代码演示了如何创建一个空 div 元素,并将其插入到页面中。

  2. 创建文本节点
    使用 document.createTextNode() 创建新文本节点,这个方法接受一个参数–要插入节点中的文本。

    var element = document.createTextNode("Hello World!");
    element.className = "message";
    var textNode = document.createElement("div");
    element.appendChild(textNode);
    document.body.appendChild(element);

    上面代码演示了如何创建一个包含文本的元素节点,并将这个节点插入页面中。

4.5 特殊集合

除了属性和方法,document 对象还有一些特殊的集合,这些集合都是 HTMLCollection 类数组:

  1. document.anchors
    包含了文档中所有带 name 特性的 元素
  2. document.links
    包含了文档中所有带 href 特性的 元素
  3. document.images
    包含了文档中所有的 元素,与 document.getElementsByTagName(“img”) 得到的结果相同
  4. document.forms
    包含了文档中所有的
    元素,与 document.getElementsByTagName(“form”) 得到的结果相同

5. element 节点

nodeType 为 1;nodeName 为元素的标签名(大写);nodeValue 为 null;parentNode 可能是 Document 或 Element;

要访问元素的标签名,可以使用 nodeName 属性,也可以使用 tagName 属性;这两个属性会返回相同的值。

5.1 特性属性

每个元素节点都有一些对应于 HTML 元素中存在的特性的属性:

  • id, 元素在文档中唯一的标识符
  • title, 有关元素的附加说明信息(通过提示条显示出来)
  • lang, 元素内容的语言代码
  • dir, 语言的方向(ltr、rtl)
  • className, 与元素的 class 特性对应,之所以改名字是因为 class 是 JavaScript 语言的保留字
<div id="myDiv" class="bd" title="Body text" lang="en" dir="ltr"></div>
var div = document.getElementById("div");
alert(div.id); // "myDiv"
alert(div.className); // "bd"
alert(div.title); // "Body text"
alert(div.lang); // "en"
alert(div.dir); // "ltr"

5.2 取得特性

操作特性的 DOM 方法有三个,分别是 getAttribute() setAttribute() removeAttribute()

var div = document.getElementById("myDiv");
alert(div.getAttribute("id")); // "myDiv"
alert(div.getAttribute("class")); // "bd"
alert(div.getAttribute("title")); // "Body text"
alert(div.getAttribute("lang")); // "en"
alert(div.getAttribute("dir")); // "ltr"
alert(div.getAttribute("haha")); // null
alert(typeof div.getAttribute("id")); // string

注意这里取得类名是用实际的特性名 class。

  • 特性也可以像 5.1 中那样通过元素属性的形式得到,是因为,特性会以属性的形式添加到元素对象中。但是,需要注意的是,只有公认的特性会添加,程序员自定义的特性不会添加到元素对象中(IE 会)。所以,如果你的元素中有自定义的特性,推荐使用 getAttribute() 方法获取特性。

  • 但是,有两个特殊的公认特性,它们虽然有对应的属性名,但属性值和通过 getAttribute() 返回的值并不相同:

    1. style
      通过属性访问它返回的是一个对象,通过 getAttribute() 方法返回的是相应代码的字符串。
    2. onclick
      通过属性访问它返回的是一个 JavaScript 函数,而通过 getAttribute() 方法返回的是相应的字符 创。
  • 所以综合上面的信息,我们需要在获取自定义的特性时,使用 getAttribute()。在获取 style,onclick 特性时通过属性访问,其它公认特性均可。

5.3 设置特性

与 getAttribute() 对应的方式是 setAttribute(),这个方法接收两个参数:要设置的特性名和值。如果特性已经存在,会以指定的值替代原来的值;如果特性不存在,创建该属性并设置相应的值。

div.setAttribute("id", "someOtherId");
div.setAttribute("dir", "ltr");

因为所有的特性都是属性,所以直接给属性赋值也可以设置特性的值:

div.id = "someOtherId";
div.dir = "ltr";

但是添加或更改自定义特性,只能通过 setAttribute() 方法。

5.4 移除特性

removeAttribute()

div.removeAttribute("id");

这个方法不仅会清除特性的值,还会将这个特性从元素中彻底清除。

5.5 attributes 属性

元素节点时使用 attributes 属性的唯一一个 DOM 节点类型。attributes 属性中包含一个 NamedNodeMap,与 NodeList 类似,也是一个动态的集合。元素的每一个特性都以一个节点的形式保存在 NamedNodeMap 对象中。

一般来说 attributes 的方法不够方便,所以开发人员更多的使用 getAttribute()、 removeAttribute()和 setAttribute() 方法操作特性。

但是在遍历元素特性的时候 attributes 属性可以派上用场。

6. text 节点

文本节点包含的是照字面量解释的纯文本内容。纯文本中可以包含转义后的 HTML 字符,但不能包含 HTML 代码。

nodeType 值为 3;nodeName 值为 “#text”;nodeValue 的值为节点所包含的文本;parentNode 是一个 element 节点;没有子节点。

6.1 规范化文本节点

DOM 文档中存在相邻的同胞文本节点很容易导致混乱,因为分不清哪个文本节点表示哪个字符串。如果在一个包含两个或多个文本节点的父元素上调用 normalize() 方法,则会将所有文本节点合并成一个节点,结果节点的 nodeValue 等于合并前每个文本节点的 nodeValue 值拼接起来的值。

不过浏览器在解析文档节点时,永远不会创建相邻的文本节点。这种情况只会作为执行 DOM 操作的结果出现。

6.2 分割文本节点

一个作用与 normalize()相反的方法: splitText(num),可以将文本在指定的位置断开,原来的文本将包含从开始到指定位置之前的内容,新文本节点将包含剩下的文本。这个方法返回的是一个新文本节点,该节点与原节点的 parentNode 相同。

7. attr 节点

从技术角度来讲,特性就是存在于元素的 attributes 属性中的节点。
尽管它们也是节点,但特性却不被认为是 DOM 文档树的一部分。开发人员最常用的是 getAttribute() setAttribute() 和 removeAttribute() 方法,很少直接引用特性节点。

本文参考:
JavaScript 高级程序设计(第3版)
JavaScript DOM编程艺术